/*****************************************************\
* Copyright (c) 2020 The game2d Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/game2d.git
* Email:          505544956@qq.com
* FileName:       core/tilemap.cc
* Create Time:    2020/03/23 13:03:22
* Description:    /
\****************************************************/
#include "tilemap.h"
#include "tinyxml2.h"

namespace g2d {
  const Sizei& TileMap::block_size() const { return block_size_; }
  void TileMap::set_block_size(const Sizei &size) { block_size_ = size; }
  const Size& TileMap::tile_size() const { return tile_size_; }
  void TileMap::set_tile_size(const Size &size) { tile_size_ = size; }
  Result TileMap::erase_tile(const char *image) {
    if (!image || !*image) return Result::E_InvalidParam;
    for (auto it = tile_map_datas_.begin(); it != tile_map_datas_.end(); ++it) {
      if (it->texture == image) {
        tile_map_datas_.erase(it);
        return Result ::S_OK;
      }
    }
    return Result::S_NotExisted;
  }
  void TileMap::clear_tile() { tile_map_datas_.clear(); }
  void TileMap::clear_map() {
    for (auto &v : map_) {
      for (auto &s : v) {
        if (s)
          s->decref();
      }
    }
    map_.clear();
  }
  bool TileMap::had_image(const char *id) {
    if (!id || !*id) return false;
    for (auto &v : tile_map_datas_) {
      if (v.texture == id) return true;
    }
    return false;
  }
  Result TileMap::set_level_file(const char *file) {
    if (!file || !*file) return Result::E_InvalidParam;
    std::string f = file;
    if (f.find("%level%") == std::string::npos)
      return Result::E_Fail;
    level_file_ = f;
    return Result::S_OK;
  }
  void TileMap::clear_level() {
    block_size_.w = 0;
    block_size_.h = 0;
    tile_size_.w = 0.f;
    tile_size_.h = 0.f;
    tile_map_datas_.clear();
    map_.clear();
  }
  void TileMap::set_attribute(const char *name, const char *value) {
    if (strcmp(name, "blocksize") == 0) set_block_size(utils::parse_sizei(value));
    else if (strcmp(name, "tilesize") == 0) set_tile_size(utils::parse_size(value));
    else if (strcmp(name, "levelfile") == 0) set_level_file(value);
    else if (strcmp(name, "level") == 0) set_cur_level(utils::parse_uint32(value));
    else if (strcmp(name, "objtype") == 0) set_obj_type(value);
    else Sprite::set_attribute(name, value);
  }
  Result TileMap::load_level() {
    clear_level();
    auto file = level_file_;
    auto pos = file.find("%level%");
    if (pos == std::string::npos)
      return Result::E_Fail;
    file.replace(pos, 7, std::to_string(level_));
    return build_level(file.c_str());
  }
  Result TileMap::build_level(const char *file) {
    if (!file || !*file) return Result::E_InvalidParam;
    tinyxml2::XMLDocument doc;
    doc.LoadFile(file);
    if (doc.Error()) return Result::E_XmlFail;
    auto root = doc.RootElement();
    if (!root || strcmp(root->Name(), "map") != 0)
      return Result::E_XmlFail;
    for (auto attr = root->FirstAttribute(); attr; attr = attr->Next()) {
      set_attribute(attr->Name(), attr->Value());
    }
    if (block_size_.w <= 0 || block_size_.h <= 0)
      return Result::E_Fail;

    const char *name = 0;
    Result rst = Result::S_OK;
    auto child = root->FirstChildElement();
    while (child) {
      name = child->Name();
      if (strcmp(name, "tile") == 0) {
        rst = add_tile(child->Attribute("image")
            , utils::parse_uint32(child->Attribute("rows"))
            , utils::parse_uint32(child->Attribute("cols"))
            , utils::parse_uint32(child->Attribute("startid")));
      } else if (strcmp(name, "data") == 0) {
        rst = set_data(child->GetText());
      }
      if (Failed(rst)) break;
      child = child->NextSiblingElement();
    }
    return rst;
  }
  Result TileMap::add_tile(const char *image, Uint32 rows, Uint32 cols, Uint32 start_id) {
    if (!image || !*image || (rows == 0 && cols == 0)) return Result::E_InvalidParam;
    if (had_image(image)) return Result::S_Existed;

    TileMapData tmd;
    tmd.texture = image;
    tmd.start_id = start_id;

    for (Uint32 j = 0; j < rows; ++j) {
      for (Uint32 i = 0; i < cols; ++i) {
        SDL_Rect rc;
        rc.x = i * tile_size_.w;
        rc.y = j * tile_size_.h;
        rc.w = tile_size_.w;
        rc.h = tile_size_.h;
        tmd.srcs.push_back(std::move(rc));
      }
    }

    tile_map_datas_.push_back(std::move(tmd));
    return Result::S_OK;
  }
  Result TileMap::set_data(const char *s) {
    if (!s) return Result::E_XmlFail;
    if (obj_type_.empty()) return Result::E_NotExistedSprite;

    auto isOKchar = [](char c) -> bool {
      if (c >= '0' && c <= '9') return true;
      if (c == '+') return true;
      if (c == '-') return true;
      return false;
    };

    char *p;
    int i = 0, j = 0;
    int d = 0;
    const char *id = 0;
    SDL_Rect *src = 0;
    Sprite *g = 0;
    std::vector<Sprite*> v;
    while (s && s[0]) {
      if (j == block_size_.h) {
        for (auto &k : v)
          k->decref();
        v.clear();
      }
      if (i == block_size_.w) {
        std::vector<Sprite*> t;
        v.swap(t);
        map_.push_back(std::move(t));
        ++j;
        i = 0;
      }
      if (!isOKchar(s[0])) {
        ++s;
        continue;
      }
      d = (int)strtol(s, &p, 10);
      if (get_image_data(d, &id, &src)) {
        g = dynamic_cast<Sprite*>(create_gobj(obj_type_.c_str()
            , "", G2D_INVALDATE_INDEX, load_res_on_enter_));
        if (!g) return Result::E_NotExistedSprite;
        g->set_tile_data(d, id, src);
      } else g = 0;
      v.push_back(g);
      ++i;
      s = p;
    }
    if (v.size() > 0)
      map_.push_back(std::move(v));
    return Result::S_OK;
  }
  void TileMap::set_obj_type(const char *type) { if (type) obj_type_ = type; }
  const char* TileMap::obj_type() const { return obj_type_.c_str(); }
  bool TileMap::get_image_data(int d, const char **id, SDL_Rect **rc) {
    for (auto &v : tile_map_datas_) {
      if (d < v.start_id) continue;
      if (d - v.start_id >= v.srcs.size()) continue;
      if (id) *id = v.texture.c_str();
      if (rc) *rc = &v.srcs[d-v.start_id];
      return true;
    }
    return false;
  }
  void TileMap::doinit() { load_level(); }
  void TileMap::update(Input &inp, Audio &aud) {
    if (!enabled_) return;
    rect_.w = tile_size_.w * block_size_.w;
    rect_.h = tile_size_.h * block_size_.h;

    Sprite *s = 0;
    SDL_FRect dst;
    dst.w = tile_size_.w;
    dst.h = tile_size_.h;
    for (int j = map_.size() - 1; j >= 0; --j) {
      for (int i = map_[j].size() - 1; i >= 0; --i) {
        s = map_[j][i];
        if (s) {
          dst.x = rect_.x + tile_size_.w * i;
          dst.y = rect_.y + tile_size_.h * j;
          s->set_rect(dst);
          if (s->enabled())
            s->update(inp, aud);
        }
      }
    }
  }
  void TileMap::render(g2d::Renderer &r) {
    if (!visible_) return;
    Sprite::render(r);
    const char *id = 0;
    SDL_Rect *rc = 0;
    SDL_FRect dst;
    dst.w = tile_size_.w;
    dst.h = tile_size_.h;
    for (auto &v : map_) {
      for (auto &s : v) {
        if (s && s->visible())
          s->render(r);
      }
    }
  }
} // end namespace g2d